/**
 * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
 */
package com.centersoft.flowable.core.web.service;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.centersoft.flowable.core.annotation.WorkFlowAuditType;
import com.centersoft.flowable.core.config.WorkFlowConfig;
import com.centersoft.flowable.core.exception.FlowableRuntimeException;
import com.centersoft.flowable.core.gateway.UserInterface;
import com.centersoft.flowable.core.model.*;
import com.centersoft.flowable.core.model.*;
import com.centersoft.flowable.core.util.WorkFlowUtils;
import com.centersoft.flowable.core.web.gateway.FlowModelGateway;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.validation.Valid;
import java.util.*;

@Service
@Transactional(readOnly = true)
public class FlowTaskClientService {
    private final RuntimeService runtimeService;
    private final TaskService taskService;
    private final HistoryService historyService;
    private final RepositoryService repositoryService;
    private final IdentityService identityService;
    private final WorkFlowConfig workFlowConfig;
    private final UserInterface userInterface;

    private final FlowModelGateway flowModelGateway;

    public FlowTaskClientService(RuntimeService runtimeService, TaskService taskService, HistoryService historyService, RepositoryService repositoryService, IdentityService identityService, WorkFlowConfig workFlowConfig, UserInterface userInterface, FlowModelGateway flowModelGateway) {
        this.runtimeService = runtimeService;
        this.taskService = taskService;
        this.historyService = historyService;
        this.repositoryService = repositoryService;
        this.identityService = identityService;
        this.workFlowConfig = workFlowConfig;
        this.userInterface = userInterface;
        this.flowModelGateway = flowModelGateway;
    }


    /**
     * 获取待办任务列表
     *
     * @return 流程
     */
    public List<ProcessVo> todoList(QueryTodoListRequest request) {

        //        String orgCode=MapUtils.getString(params,"orgCode");
        //        DataSource dataSource=dataSourceService.findOneByProMark(MapUtils.getString(params,"proMark"));
        TaskQuery todoTaskQuery = taskService.createTaskQuery().taskCandidateOrAssigned(request.getUserId()).taskTenantId(request.getTaskTenantId()).active().includeProcessVariables().orderByTaskCreateTime();
        if (BooleanUtils.isTrue(request.getAsc())) {
            todoTaskQuery.asc();
        } else {
            todoTaskQuery.desc();
        }

        if (!CollectionUtils.isEmpty(request.getMenuCodes())) {
            todoTaskQuery.processCategoryIn(request.getMenuCodes());
        }


        // 设置查询条件
        List<Task> todoList = todoTaskQuery.list();

        List<ProcessVo> processVos = new ArrayList<>();
        for (Task task : todoList) {
            Process process = repositoryService.getBpmnModel(task.getProcessDefinitionId()).getMainProcess();
            ProcessVo processVo = new ProcessVo();
            TaskVo taskVo = new TaskVo(task);
            taskVo.setProcessDefKey(process.getId());
            // processVo.setTask(taskVo);
            processVo.setVars(task.getProcessVariables());
            processVo.setMenuCode(task.getCategory());
            processVo.setBillcode(MapUtils.getString(task.getProcessVariables(), "billcode"));
            processVo.setProcessDefinitionName(task.getName());
            processVo.setTask(taskVo);
            processVos.add(processVo);
        }

        return processVos;
    }

    public void delHistoryDataByProcInsId(String procInsId) {
        if (StringUtils.isNotBlank(procInsId)) {
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(procInsId).singleResult();
            if (null != processInstance) {
                runtimeService.deleteProcessInstance(procInsId, "流程实例删除");
            } else {
                historyService.deleteHistoricProcessInstance(procInsId);
            }
        } else {
            throw new FlowableRuntimeException("为未获取到历史数据");
        }
    }

    public Boolean isCanRecall(String procInsId, String taskUserId) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(procInsId).singleResult();
        if (null == processInstance) {
            return false;
        }
        List<HistoricTaskInstance> finishedTasks = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstance.getId()).finished().orderByHistoricTaskInstanceEndTime().desc().list();
        if (org.springframework.util.CollectionUtils.isEmpty(finishedTasks)) {
            // 若流程尚未有人审批，再判断发起人是否是当前人员
            Map<String, Object> variables = runtimeService.getVariables(processInstance.getId());
            String initiator = (String) variables.get("applyUserId");
            return initiator.equals(taskUserId);
        } else {
            return false;
        }
    }

    public Boolean isMyAudit(String procInsId, String currentUserId) {
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInsId).singleResult();
        if (null == processInstance) {
            return false;
        }
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).active().list();
        for (Task task : tasks) {
            String assignee = task.getAssignee();
            if (StringUtils.isNotEmpty(assignee)) {
                // 有办理人，验证办理人是否是当前人员
                if (currentUserId.equals(assignee)) return true;
            } else {
                // 没有办理人，获取是否有候选人
                List<IdentityLink> identityLinksForTask = taskService.getIdentityLinksForTask(task.getId());
                if (org.springframework.util.CollectionUtils.isEmpty(identityLinksForTask)) break;
                for (IdentityLink identityLink : identityLinksForTask) {
                    // 验证候选人中是否有当前人员
                    String userId = identityLink.getUserId();
                    if (currentUserId.equals(userId)) return true;
                }
            }
        }

        return false;
    }

    public String startProcessForClient(StartProcessRequest startProcessRequest) {
        String menuCode = startProcessRequest.getMenuCode();
        String assigneeTypes = startProcessRequest.getAssigneeTypes();


        String orgCode = startProcessRequest.getOrgCode();

        // 判断是否未多组织,根据组织编号获取对应的流程
        String key;
        QueryTaskRequest request = new QueryTaskRequest();

        request.setMenuCode(menuCode);
        if (workFlowConfig.getEnableMulitOrg() && StringUtils.isNotBlank(orgCode)) {
            // 通过项目,组织,菜单三个条件查询流程
            request.setOrgCode(orgCode);
            key = this.getWorkFlowKeyByMenuCodeAndTentIdAndOrgCode(request);
        } else {

            key = this.getWorkFlowKeyByMenuCodeAndTentIdAndOrgCode(request);
        }

        ReviewRequest reviewRequest = ReviewRequest.fromStartRequest(startProcessRequest);
        reviewRequest.setProcDefKey(key);
        reviewRequest.setWorkFlowAuditType(WorkFlowAuditType.start);
        // 开启流程审批
        String procInsId = this.startProcess(reviewRequest);

        // 如果前端选择了审批人，则修改之前的审批人信息
        if (StringUtils.isNotEmpty(assigneeTypes)) {
            changeNextAssignee(assigneeTypes, procInsId);
        }
        return procInsId;
    }

    private void changeNextAssignee(String assigneeTypes, String procInsId) {
        List<FlowAssigneeModel> assigneeTypeVos = JSON.parseArray(assigneeTypes, FlowAssigneeModel.class);
        if (org.flowable.editor.language.json.converter.util.CollectionUtils.isNotEmpty(assigneeTypeVos)) {
            if ("user".equals(assigneeTypeVos.get(0).getType())) {
                // 查询选择条件筛选出的审核人
                Set<String> candidateUsers = userInterface.getUserByAssigneeTypeVo(assigneeTypeVos);
                HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInsId).singleResult();
                List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).active().list();
                for (Task task : tasks) {
                    String assignee = task.getAssignee();
                    if (!candidateUsers.contains(assignee)) {
                        runtimeService.deleteParticipantUser(task.getProcessInstanceId(), assignee);
                        runtimeService.deleteMultiInstanceExecution(task.getExecutionId(), false);
                        runtimeService.deleteUserIdentityLink(task.getProcessInstanceId(), assignee, "participant");
                        historyService.deleteHistoricTaskInstance(task.getId());
                    }
                }
            } else {
                HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInsId).singleResult();
                List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstance.getId()).active().list();
                if (!CollectionUtils.isEmpty(tasks) && tasks.size() == 1) {
                    // 查询选择条件筛选出的审核人
                    Set<String> candidateUsers = userInterface.getUserByAssigneeTypeVo(assigneeTypeVos);
                    if (CollectionUtils.isEmpty(candidateUsers)) {
                        throw new FlowableRuntimeException("审核任务为空");
                    }

                    // 删除默认的审核人
                    Task task = tasks.get(0);
                    deleteAssigneeAndCandidateUsers(task);

                    // 重新写入审核人
                    for (String candidateUser : candidateUsers) {
                        taskService.addCandidateUser(task.getId(), candidateUser);
                    }
                }
            }
        }
    }

    private void deleteAssigneeAndCandidateUsers(Task task) {
        String assignee = task.getAssignee();
        String taskId = task.getId();
        if (StringUtils.isNotBlank(assignee)) {
            // 有审核人删除审核人
            task.setAssignee(null);
        } else {
            // 有待办人删除待办人
            List<IdentityLink> identityLinksForTask = taskService.getIdentityLinksForTask(taskId);
            if (org.flowable.editor.language.json.converter.util.CollectionUtils.isNotEmpty(identityLinksForTask)) {
                for (IdentityLink identityLink : identityLinksForTask) {
                    taskService.deleteCandidateUser(taskId, identityLink.getUserId());
                }
            }
        }
    }

    /**
     * 启动流程
     *
     * @param reviewRequest 审核参数
     * @return 流程实例ID
     */
    @SuppressWarnings("unused")
    @Transactional
    public String startProcess(ReviewRequest reviewRequest) {

        String userId = reviewRequest.getApplyUserId();

        if (userId == null) {
            throw new FlowableRuntimeException("流程审批人获取失败");
        }

        // 用来设置启动流程的人员ID，引擎会自动把用户ID保存到activiti:initiator中
        identityService.setAuthenticatedUserId(userId);


        if (reviewRequest.getProcDefKey() != null) {
            String businessKey = reviewRequest.getBillcode();
            //验证是否已发起流程，如果已发起则直接返回原流程实例ID
            reviewRequest.setWorkFlowAuditType(WorkFlowAuditType.start);

            ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
            if (StringUtils.isNotBlank(workFlowConfig.getTenantId())) {
                processInstanceQuery.processInstanceTenantId(workFlowConfig.getTenantId());
            }
            List<ProcessInstance> list = processInstanceQuery.processInstanceBusinessKey(businessKey).active().list();
            if (CollectionUtil.isNotEmpty(list)) {
                //这里获取原来的流程实例id后需要调用业务系统去更新单据状态
                String procInsId = list.get(0).getId();
                reviewRequest.setProcInsId(procInsId);
                WorkFlowUtils.updateBusinessByType(reviewRequest);
                return list.get(0).getId();
            }
            ProcessInstance procIns = null;
            if (StringUtils.isBlank(workFlowConfig.getTenantId())) {
                procIns = runtimeService.startProcessInstanceByKey(reviewRequest.getProcDefKey(), businessKey, reviewRequest.toMap());
            } else {
                procIns = runtimeService.startProcessInstanceByKeyAndTenantId(reviewRequest.getProcDefKey(), businessKey, reviewRequest.toMap(), workFlowConfig.getTenantId());
            }
            return procIns.getId();
        } else {
            //由于这个报错没有影响erp业务，这里归为erp的errorCode方便把异常抛出去提示客户！
            throw new FlowableRuntimeException("未获取到流程!!!!");
        }
    }


    public String getWorkFlowKeyByMenuCodeAndTentIdAndOrgCode(@Valid QueryTaskRequest request) {
        request.setTenantId(workFlowConfig.getTenantId());
        List<FlowModelModel> models = flowModelGateway.listModel(request);


        //获取最新的已发布的流程key，没发布的模型不取
        for (FlowModelModel map : models) {
            String modelKey = map.getKey();
            List<Deployment> list = repositoryService.createDeploymentQuery().deploymentKey(modelKey).list();
            if (org.flowable.editor.language.json.converter.util.CollectionUtils.isNotEmpty(list)) {
                return modelKey;
            }
        }

        //没取到抛出异常
        throw new FlowableRuntimeException("未获取到对应组织的流程信息");
    }

    /**
     * 流程中途时验证是否需要选择具体审核人
     */
    public Map<String, Map<String, Object>> getNextAssigneeOnTask(Task task, String modelKey, Map<String, Object> variables) {
        return null;
    }

    public List<FlowAssigneeModel> getMultiInstanceAssigneeChooses(Map<String, Map<String, Object>> nextAssigneeOnTask, Map<String, Object> vars) {
        return null;
    }

    public List<FlowAssigneeModel> getAssigneeTypeVos(Map<String, Map<String, Object>> nextAssigneeTypes) {
        return null;
    }

}
